<template>
  <div class="va-df-input-inner-wrapper">
    <div
      ref="field"
      :is="componentType"
      :disabled="disabled"
      :form-submitting="formSubmitting"
      :field="field"
      :data-source="dataSource"
      :value="value"
      :style="[field.inputStyle]"
      :class="[field.inputClass]"
      :is-loading="isLoading"
      @upload="onUpload"
      @submit="submit"
      @input="onChange"></div>
    <i v-if="pending" class="va-icon-spinner-fish ann-rotation va-df-input-pending"></i>
    <div v-if="isLoading" class="va-df-input-loading-mask">
      <i class="va-icon-spinner-fish ann-rotation"></i>
    </div>
  </div>
</template>

<script>
  import Vue from 'vue'
  import _ from 'lodash'
  import validateRules from '../validations'
  import InputTypes from '../input-types'
  import Emitter from '../utils/emitter-mixin'

  function validateNextRule (vm, val, curIdx, validators, model, formModel, field, fieldScope) {
    if (validators.length <= curIdx) {
      vm.setError({
        field: vm.field,
        model: vm.model
      })
      // Vue.set(vm.model, field.name, val)
      return
    }
    let validator = validators[curIdx]
    let rst = validator.rule.validate(val, validator.config, model, formModel, field, fieldScope)
    if (rst.valid.result.then && rst.valid.result.then instanceof Function) {
      vm.setValidatePending(true)
      rst.valid.result.then(valid => {
        if (!valid) {
          vm.setError({
            field: vm.field,
            model: vm.model,
            rule: validator.rule.rule,
            errorTemplate: rst.valid.errorMessage || rst.errorTemplate,
            errorParams: rst.valid.errorMessage ? null : rst.errorParams
          })
        } else {
          curIdx++
          validateNextRule(vm, val, curIdx, validators, model, formModel, field, fieldScope)
        }
      })['finally'](() => {
        vm.setValidatePending(false)
      })
    } else {
      if (rst.valid.result) {
        curIdx++
        validateNextRule(vm, val, curIdx, validators, model, formModel, field, fieldScope)
      } else {
        vm.setError({
          field: vm.field,
          model: vm.model,
          rule: validator.rule.rule,
          errorTemplate: rst.valid.errorMessage || rst.errorTemplate,
          errorParams: rst.valid.errorMessage ? null : rst.errorParams
        })
      }
    }
  }

  /*
  * Form项组件
  * */
  export default {
    name: 'DynamicFormInputField',
    mixins: [Emitter],
    props: {
      index: Number,
      value: {},
      formSubmitting: {
        type: Boolean,
        default: false
      }
    },
    mounted () {
      this.init()
    },
    data () {
      return {
        isLoading: false,
        debounceValidateNextRule: null,
        debounceValidateSync: _.debounce(this.validateSync, 200, {leading: false}),
        loadTimes: 0,
        dataSource: [],
        validators: [],
        pending: false
      }
    },
    destroyed () {
      this.$emit('field-destroyed', {field: this.field, model: this.model})
      this.form && (delete this.form.$fields[this.key])
      if (this.form.$validationDependsRelation && this.form.$validationDependsRelation[this.key]) {
        delete this.form.$validationDependsRelation[this.key]
      }
      if (this.form.$dataSourceDependsRelation && this.form.$dataSourceDependsRelation[this.key]) {
        delete this.form.$dataSourceDependsRelation[this.key]
      }
    },
    computed: {
      tabIndex () {
        return 0
      },
      componentType () {
        return typeof this.field.componentType === 'string' ? InputTypes[this.field.componentType] : this.field.componentType
      },
      form () {
        return this.getDynamicForm()
      },
      commonClasses () {
        return (this.form && this.form.formSchema && this.form.formSchema.commonClasses) || {}
      },
      /*
      * 最近的具有model的组件（FormGroup或者DynamicForm）
      * */
      nmcComponent () {
        return this.findNMCParent()
      },
      /*
      * 上一层的Model，如果没有则等于formModel
      * */
      model () {
        return (this.nmcComponent && this.nmcComponent.model) || {}
      },
      formModel () {
        return (this.nmcComponent && this.nmcComponent.formModel) || {}
      },
      field () {
        return (this.nmcComponent && this.nmcComponent.field) || {}
      },
      disabled () {
        if (this.formSubmitting) {
          return true
        }
        if (_.isNil(this.field.disabled)) {
          return false
        }
        if (_.isFunction(this.field.disabled)) {
          return this.field.disabled(this.model, this.formModel, this.field)
        }
        return !!this.field.disabled
      }
    },
    methods: {
      init () {
        this.key = `${this.model.__key}_${this.field.name}`

        let form = this.form
        let context = {}

        if (form) {
          if (_.isNil(form.$fields)) {
            // 将Input对应的Field、Model及Context存储到form的$fields中，为了关联验证时使用
            form.$fields = {}
          }

          form.$fields[this.key] = {
            vm: this,
            model: this.model,
            field: this.field
          }

          context = form.$fields[this.key]
        }

        if (this.model[this.field.name] === undefined) {
          Vue.set(this.model, this.field.name, null)
        }

        if (_.isFunction(this.field.dataSource)) {
          context.reload = _.debounce(() => {
            this.loadTimes++
            let curTime = this.loadTimes
            let rst = this.field.dataSource(this.model, this.formModel, this.field)
            if (rst && rst.then && rst.then instanceof Function) {
              this.setLoading(true)
              rst.then(data => {
                this.dataSource = data
              })['finally'](() => {
                if (this.loadTimes === curTime) {
                  this.setLoading(false)
                }
              })
            } else if (_.isArray(rst)) {
              this.dataSource = rst
            }
          }, 200)
          context.reload()

          // dataSource dependence
          if (this.field.dataSourceDependsOn && form) {
            if (!form.$dataSourceDependsRelation) {
              form.$dataSourceDependsRelation = {}
            }
            _.chain(this.field.dataSourceDependsOn.split(','))
              .map(item => {
                return item.trim()
              })
              .filter(item => {
                return !!item
              })
              .forEach(item => {
                let key = `${this.model.__key}_${item}`
                if (form.$dataSourceDependsRelation[key]) {
                  form.$dataSourceDependsRelation[key].push(this.field.name)
                } else {
                  form.$dataSourceDependsRelation[key] = [this.field.name]
                }
              }).value()
          }
        } else if (_.isArray(this.field.dataSource)) {
          this.dataSource = this.field.dataSource
        }

        this.debounceValidateNextRule = _.debounce(validateNextRule, this.field.delay || 200, {leading: false})

        let rules = []
        if (this.field.validators) {
          _.forEach(this.field.validators, (value, key) => {
            if (key === 'dependsOn' && form) {
              if (!form.$validationDependsRelation) {
                form.$validationDependsRelation = {}
              }
              _.chain(value.split(','))
                .map(item => {
                  return item.trim()
                }).filter(item => {
                  return !!item
                }).forEach(item => {
                  let key = `${this.model.__key}_${item}`
                  if (form.$validationDependsRelation[key]) {
                    form.$validationDependsRelation[key].push(this.field.name)
                  } else {
                    form.$validationDependsRelation[key] = [this.field.name]
                  }
                }).value()
              return
            }
            let rule = validateRules[key]
            if (!rule) {
              throw new Error(`rule named ${key} not exist`)
            }
            rules.push({
              rule: rule,
              config: this.field.validators
            })
          })
          rules = _.orderBy(rules, [item => {
            return item.rule.priority
          }])
        }
        this.validators = rules

        this.validateSync()
      },
      submit () {
        this.form && this.form.$el.dispatchEvent(new Event('submit'))
      },
      onUpload (isUploading) {
        this.dispatch(['DynamicFormFormGroup'], 'upload', {field: this.field, model: this.model, isUploading})
      },
      setLoading (isLoading) {
        this.isLoading = isLoading
        this.dispatch(['DynamicFormFormGroup'], 'loading', {field: this.field, model: this.model, isLoading})
      },
      setError (params) {
        Vue.set(this.field, '$error', params)
        this.dispatch(['DynamicFormFormGroup'], 'validate', params)
      },
      setValidatePending (isPending) {
        this.pending = isPending
        this.dispatch(['DynamicFormFormGroup'], 'pending', {field: this.field, model: this.model, isPending})
      },
      onChange (val) {
        if (val === this.model[this.field.name]) {
          return
        }
        Vue.set(this.model, this.field.name, val)
        this.debounceValidateSync(val)
        let form = this.form
        if (form) {
          if (form.$validationDependsRelation && form.$validationDependsRelation[this.key]) {
            _.forEach(form.$validationDependsRelation[this.key], item => {
              let field = form.$fields[`${this.model.__key}_${item}`]
              if (field) {
                field.vm.debounceValidateSync()
              }
            })
          }
          if (form.$dataSourceDependsRelation && form.$dataSourceDependsRelation[this.key]) {
            _.forEach(form.$dataSourceDependsRelation[this.key], item => {
              let field = form.$fields[`${this.model.__key}_${item}`]
              if (field) {
                field.vm.dataSource = []
                field.reload && field.reload()
              }
            })
          }
        }
      },
      validateSync (val) {
        if (val === undefined) {
          val = this.model[this.field.name]
        }
        this.debounceValidateNextRule && this.debounceValidateNextRule(this, val, 0, this.validators, this.model, this.formModel, this.field, this.form && this.form.$fields)
      }
    },
    watch: {
      value (newVal, oldVal) {
        if (newVal !== oldVal) this.debounceValidateSync(newVal)
      },
      model (newV, oldV) {
        if (newV !== oldV) {
          if (oldV && oldV.__key && oldV.__key !== newV.__key) {
            delete this.form.$fields[`${oldV.__key}_${this.field.name}`]
          }
          this.init()
        }
      }
    }
  }
</script>
